package top.xzxsrq.web.download.utils;

import lombok.extern.log4j.Log4j2;
import org.htmlunit.*;
import org.htmlunit.html.DomElement;
import org.htmlunit.html.FrameWindow;
import org.htmlunit.html.HtmlPage;
import org.htmlunit.util.FalsifyingWebConnection;
import top.xzxsrq.utils.StringUtilsZX;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Log4j2
public class HtmlUnits {
    private WebClient webClient;
    private HtmlPage page = null;
    private final InterceptWebConnection interceptWebConnection;

    public HtmlUnits() {
        this(BrowserVersion.CHROME);
    }

    public HtmlUnits(BrowserVersion browserVersion) {
        // HtmlUnit 模拟浏览器
        webClient = new WebClient(browserVersion);
        webClient.getOptions().setJavaScriptEnabled(true);              // 启用JS解释器，默认为true
        webClient.getOptions().setCssEnabled(false);                    // 禁用css支持
        webClient.getOptions().setThrowExceptionOnScriptError(false);   // js运行错误时，是否抛出异常
        webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); //当HTTP的状态非200时是否抛出异常
        webClient.getOptions().setTimeout(60 * 1000);                   // 设置“浏览器”的请求超时时间
        webClient.getOptions().setUseInsecureSSL(true);
        webClient.setAjaxController(new NicelyResynchronizingAjaxController());//很重要，设置支持AJAX
        webClient.addRequestHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
        webClient.setJavaScriptTimeout(20 * 1000); // 设置JS执行的超时时间，可以强行打断alert这样的代码，让代码继续执行
        interceptWebConnection = new InterceptWebConnection(webClient);
    }

    public void showUrl(String pageUrl) throws Exception {
        interceptWebConnection.requestUrls.clear();
        log.info("pageUrl = " + pageUrl);
        page = webClient.getPage(pageUrl);
        log.info("页面获取完成：{}", pageUrl);
    }

    public List<FrameWindow> getIframes() {
        return page.getFrames();
//        for (FrameWindow frame : page.getFrames()) {
//            HtmlPage enclosingPage = frame.getEnclosingPage(); // 获取自己
//            HtmlPage enclosingPage = (HtmlPage)frame.getEnclosedPage(); // 获取子集
//            BaseFrameElement frameElement = frame.getFrameElement();
//            log.info("frameElement = " + frameElement);
//        }
//        for (WebWindow webWindow : webClient.getWebWindows()) { // 获取全部。平铺了
//            HtmlPage enclosedPage = (HtmlPage)webWindow.getEnclosedPage();
//            for (DomElement iframe : enclosedPage.getElementsByTagName("iframe")) {
//                log.info("iframe = " + iframe);
//            }
//        }
    }

    public HtmlPage getPage() {
        return page;
    }

    public String getStringPage() {
        if (page != null) {
            return page.asXml();
        }
        return "";
    }

    public List<String> getResources() {
        return this.getResources(new String[]{".mp4", ".m3u8"});
    }

    public List<String> getResources(String[] resourcesType) {
        Pattern r = resourcesType2Pattern(resourcesType);
        return interceptWebConnection.requestUrls
                .stream()
                .filter(i -> {
                    Matcher m = r.matcher(i);
                    return m.matches();
                })
                .collect(Collectors.toList());
    }

    /**
     * 注意 htmlunit不是真实浏览器，video和audio标签不会发出请求，监听不到是正常的
     * @return
     */
    public List<String> getVideoAndAudioUrl() {
        List<String> result = new LinkedList<>();
        List<DomElement> video = findDom(this, "video");
        for (DomElement domElement : video) {
            getDomSrcToResult(result,domElement);
        }
        List<DomElement> audio = findDom(this, "audio");
        for (DomElement domElement : audio) {
            getDomSrcToResult(result,domElement);
        }
        return result;
    }

    private static void getDomSrcToResult(List<String> result,DomElement domElement) {
        String src = domElement.getAttribute("src");
        if(StringUtilsZX.isEmpty(src)){
            return;
        }
        if(src.startsWith("blob")){
            return;
        }
        result.add(src);
    }

    public static List<DomElement> findDom(HtmlUnits htmlUnits, String elName) {
        List<DomElement> result = new LinkedList<>();
        forEachIframes(htmlUnits.getIframes(),(frameWindow)->{
            HtmlPage enclosedPage = (HtmlPage)frameWindow.getEnclosedPage();
            List<DomElement> elementsByName = enclosedPage.getElementsByTagName(elName);
            result.addAll(elementsByName);
            return null;
        });
        return result;
    }


    public static void forEachIframes(List<FrameWindow> iframes, Function<FrameWindow,Void> call) {
        for (FrameWindow iframe : iframes) {
            HtmlPage enclosedPage1 = (HtmlPage) iframe.getEnclosedPage();
            if(enclosedPage1 != null) {
                forEachIframes(enclosedPage1.getFrames(),call);
            }
            call.apply(iframe);
        }
    }

    public static Pattern resourcesType2Pattern(String[] resourcesType) {
        StringBuilder pattern = new StringBuilder("http.*?(");
        for (int i = 0; i < resourcesType.length; i++) {
            if (i == resourcesType.length - 1) {
                pattern.append("\\").append(resourcesType[i]);
            } else {
                pattern.append("\\").append(resourcesType[i]).append("|");
            }
        }
        pattern.append(")");
        return Pattern.compile(pattern.toString());
    }

    public void close() {
        this.webClient.close();
        this.webClient = null;
    }
}


@Log4j2
class InterceptWebConnection extends FalsifyingWebConnection {
    public InterceptWebConnection(WebClient webClient) throws IllegalArgumentException {
        super(webClient);
    }

    public List<String> requestUrls = new LinkedList<>();

    /**
     * 这里可以做请求拦截
     *
     * @param request
     * @return
     * @throws IOException
     */
    @Override
    public WebResponse getResponse(WebRequest request) throws IOException {
        requestUrls.add(request.getUrl().toString());
        return super.getResponse(request);
    }
}

